#!/usr/bin/python

import copy
import random
import sys

import data
import settings

class Piece:
    "A Piece contains a _kind, _colour, _coor, _xoffset, _yoffset"
    
    def __init__(self, pieceorkind, random_colour=False):
        piece = False

        if type(pieceorkind) is str:
            self._kind = pieceorkind
        else:
            piece = True
            self._kind = pieceorkind._kind 

        self._coor = data.kind_to_coor[self._kind]

        if piece:
            self._colour = pieceorkind._colour
        else:
            if random_colour:
                self._colour = random.choice(data.colours)
            else:
                self._colour = data.kind_to_colour[self._kind]

        self._height = 0
        (self._xoffset,self._yoffset) =\
            data.kind_to_offset[self._kind]


    def movePiece(self,dir):
        if dir == "Left":
            self._coor = [(x-1,y) for (x,y) in self._coor]
            self._xoffset = self._xoffset - 1

        elif dir == "Right":
            self._coor = [(x+1,y) for (x,y) in self._coor]
            self._xoffset = self._xoffset + 1

        elif dir == "Down":
            self._coor = [(x,y+1) for (x,y) in self._coor]
            self._yoffset = self._yoffset + 1

        elif dir == "Up":
            self._coor = [(x,y-1) for (x,y) in self._coor]
            self._yoffset = self._yoffset - 1

        elif dir == "RotateCCW":
            if self._kind != "O":
                self._coor = [(self._xoffset - self._yoffset + y,
                    self._xoffset + self._yoffset - x) for (x,y) in self._coor]

        elif dir == "RotateCW":
            if self._kind != "O":
                self._coor = [(self._xoffset + self._yoffset - y,
                    self._yoffset - self._xoffset + x) for (x,y) in self._coor]


    def centrePiece(self):
        self._coor = [(x+5,y) for (x,y) in self._coor]
        self._xoffset = self._xoffset + 5


    def checkBounds(self):
        "Return true if the piece is within the bounds of the board."
        
        for (x,y) in self._coor:
            if x < 0 or x > (data.board_width-1) or y < 0 or y > (data.board_height-1):
                return False
        return True


    def checkBottom(self):
        "Return true if the piece is at the bottom of the board."

        for (x,y) in self._coor:
            if y > (data.board_height-1): return True
        return False


class Board:
    "A Board is simply a dictionary from (int,int) -> colour"

    def __init__(self):
        self.board = dict([((x,y),"None")
            for x in range(0,data.board_width) for y in range(0,data.board_height)])
    

    def putPiece(self,piece):
        """Puts the piece on the board (i.e. changes the coordinates of the
        piece on the board to be the colour of the piece."""

        for coor in piece._coor:
            self.board[coor] = piece._colour


    def checkCollide(self,piece):
        "Returns true if this piece collides with another on the board."

        for coor in piece._coor:
            try:
                if self.board[coor] != "None":
                    return True
            except KeyError:
                pass

        return False
    

    def eraseLines(self):
        "Erases full lines on the board."
        
        numlines = 0

        for row in range(0,data.board_height):
            # Assume the row is full
            rowfull = True
            for coor in [(x,row) for x in range(0,data.board_width)]:
                # If any of the elements in the row are None, the row is
                # not full and we break the loop to check the next row.
                if self.board[coor] == "None":
                    rowfull = False
                    break
            
            if rowfull:
                numlines = numlines + 1
                # If the row is full, copy the values in row-1 down to row
                # Row 0 will not be copied on to, it will be filled with None's
                for (x,y) in [(x,y)
                    for y in range(row,0,-1)
                    for x in range(0,data.board_width)]:
                    self.board[(x,y)] = self.board[(x,y-1)]

                #Fill the top row with "None"s
                for coor in [(x,0) for x in range(0,
                    data.board_width)]:
                    self.board[coor] = "None"
   
        return numlines


class GameState:
    "A GameState has a _board, a currently active _piece, a _nextpiece and a _holdpiece"

    def __init__(self):
        self.settings = settings.Settings()

        
    def new(self):
        self._canswitch = True 
        self._piecelist = ["O","S","Z","J","L","I","T"]
        self._pieceindex = 6

        self.score = 0
        self.lines = 0
        self.level = self.settings.value("setup/starting_level")
        self.random_colours = self.settings.value("gameplay/random_colours")

        self.board = Board()
        self.piece = self.new_piece()
        self.piece.centrePiece()
        self.nextpiece = self.new_piece()
        self.holdpiece = None

        self.gameover = False
        self.paused = False


    # Generate piece via the Tetris Guidelines
    # Permute the list of pieces, then select them one at a time from this
    # list and repeat.  This reduces the amount of successive similar or nasty
    # chains or time between two Is.
    def new_piece(self):
        if self._pieceindex == 6:
            self._pieceindex = 0
            random.shuffle(self._piecelist)
        else:
            self._pieceindex += 1

        return Piece(self._piecelist[self._pieceindex], self.random_colours)
  

    def hold_piece(self):
        if not self.paused and self._canswitch:
            if self.holdpiece != None:
                # Swap the hold and current pieces
                temppiece = Piece(self.piece)
                self.piece = Piece(self.holdpiece)
                self.piece.centrePiece()
                self.holdpiece = Piece(temppiece)
            else:
                self.holdpiece = Piece(self.piece)
                self.next_piece()

            self._canswitch = False

            return True
        return False


    def next_piece(self):
        self.piece = Piece(self.nextpiece)
        self.piece.centrePiece()
        self.nextpiece = self.new_piece()

        if self.board.checkCollide(self.piece):
            self.gameover = True


    def hard_drop(self, shadow=None):
        piece = copy.deepcopy(self.piece)

        while not (piece.checkBottom() or
            self.board.checkCollide(piece)):
            piece.movePiece("Down")
            piece._height = piece._height + 1
        piece.movePiece("Up")

        if shadow != None:
            return piece
        elif not self.paused:
            # Give the piece a _height attribute to calculate score
            piece._height = piece._height - 1
            return self.move_done(piece)


    def make_move(self,dir):
        "Given a direction, it checks if the move is valid and then moves."
        
        # Only move if we're not paused
        if not self.paused:
            # Make a copy of the current piece and move it (to do the tests)
            movedPiece = copy.deepcopy(self.piece)
            movedPiece.movePiece(dir)
            
            if dir == "Down" and (movedPiece.checkBottom() or
                self.board.checkCollide(movedPiece)):

                return self.move_done()

            elif dir == "Down" or (movedPiece.checkBounds() and
                not self.board.checkCollide(movedPiece)):

                self.piece = movedPiece

            # In this case, we have chosen to rotate and the piece either
            # collides with another, or goes outside of the wall. Attempt a
            # wall kick.
            elif (dir in ["RotateCW", "RotateCCW"]) and\
                (not movedPiece.checkBounds() or
                self.board.checkCollide(movedPiece)):

                movedPiece.movePiece("Left")

                if (movedPiece.checkBounds() and
                    not self.board.checkCollide(movedPiece)):

                    self.piece = movedPiece

                else:
                    movedPiece.movePiece("Right")   
                    movedPiece.movePiece("Right")   

                    if (movedPiece.checkBounds() and
                        not self.board.checkCollide(movedPiece)):

                        self.piece = movedPiece


    def move_done(self,piece=None):
        if piece == None:
            piece = self.piece

        before = self.lines / 10

        self.board.putPiece(piece)
        lines = self.board.eraseLines()

        self.lines = self.lines + lines

        score = {
            0 : 0,
            1 : self.level * 100,
            2 : self.level * 300,
            3 : self.level * 500,
            4 : self.level * 800
        }[lines]

        self.score = self.score + score + 2*piece._height

        if self.lines / 10 > before:
            self.level += 1

        self.next_piece()
        self._canswitch = True

        # Return some info that can be used for animations
        return (piece._xoffset, score + 2*piece._height)
        

